在過去的十幾天裡,我們透過 Apple 經典的 MVC (Model-View-Controller) 架構,從零到有成功打造了一個功能齊全的鬧鐘 App。這證明了 MVC 作為一個入門框架的價值,它簡單直觀。
然而,當我們完成所有功能,回顧 MainViewController.swift
時,卻發現這個檔案已經變成一個龐然大物。它裡面有 UI 設定、有 UITableView
的代理方法、有 Realm 資料庫的讀寫、有刪除鬧鐘的邏輯、有檢查過期鬧鐘的規則,甚至還有格式化顯示文字的輔助函式。
困惑點:MVC 模式的瓶頸在哪裡?隨著專案功能的堆疊,我親身體驗到了 MVC 在複雜場景下的幾個典型痛點,也就是常說的「Massive View Controller (重量級視圖控制器)」問題
有關 Massive View Controller 的幾個討論及文章:
AI 協作
我的鬧鐘 App 現在功能都完成了,但我覺得 MainViewController 好臃腫,裡面有 UI 設定、有 Realm 資料庫操作、還有各種格式化文字的邏輯,全部混在一起。未來如果想修改或測試都很困難。在 iOS 開發中有沒有更好的架構模式可以解決?請簡單說明並不用給程式碼
AI 的回答:
在 iOS 開發中,您遇到的 MainViewController 職責過多,也就是俗稱的「臃腫視圖控制器」(Massive View Controller) 問題,這是一個非常普遍的挑戰。為了解決這個問題,許多架構模式應運而生,它們的共同目標都是將原本集中在
ViewController
的邏輯分散到不同的物件中,以實現關注點分離(Separation of Concerns),讓程式碼更易於維護、測試和擴展。以下是幾個常見且能解決您問題的架構模式,我將簡單介紹它們的核心概念:
MVC (Model-View-Controller)
MVC 是 Apple 官方推薦的基礎模式,但它也常常是造成您問題的根源。許多開發者誤解了其定義,將所有邏輯塞入 Controller。
- Model (模型):負責處理資料和業務邏輯。例如:您的鬧鐘資料(時間、名稱、是否啟用)、鬧鐘資料的儲存(如 Realm 資料庫操作)。
- View (視圖):負責使用者介面。例如:顯示鬧鐘列表的
UITableView
、按鈕和標籤。- Controller (控制器):作為 Model 和 View 之間的橋樑。它接收使用者在 View 上的操作(如點擊),並更新 Model;同時,當 Model 資料變動時,它也會更新 View 的顯示。
您目前的問題是 Controller 承擔了太多 Model 的責任(如 Realm 操作)和 View 的責任(如 UI 設定和格式化邏輯)。要解決這個問題,您需要將這些邏輯從
MainViewController
中抽離。MVVM (Model-View-ViewModel)
MVVM 是一個非常流行的模式,它旨在解決傳統 MVC 中 Controller 變得臃腫的問題。
- Model (模型):與 MVC 中的 Model 相同,處理資料和業務邏輯(例如您的鬧鐘資料和 Realm 操作)。
- View (視圖):與 MVC 中的 View 相同,只負責顯示使用者介面,並且只包含最少量的 UI 邏輯。
- ViewModel (視圖模型):這是 MVVM 的核心。它是一個獨立於 View 的物件,負責管理 View 的狀態和邏輯。它會從 Model 取得資料,並將其處理成 View 可以直接顯示的格式。ViewModel 並不知道 View 的存在,View 則會「綁定」(bind) 到 ViewModel 的屬性,當 ViewModel 中的資料改變時,View 會自動更新。
在您的鬧鐘 App 中,您可以創建一個
AlarmListViewModel
。這個 ViewModel 將負責:
- 從 Realm 讀取和寫入鬧鐘資料。
- 將原始資料格式化成 View 可以直接顯示的字串(如將日期轉換為
HH:mm
格式)。- 處理使用者操作的邏輯,例如點擊啟用/停用鬧鐘。
MainViewController
只需要持有這個AlarmListViewModel
,並將其屬性綁定到 UI 上即可,如此一來,它將變得非常輕量。VIPER (View-Interactor-Presenter-Entity-Router)
VIPER 是一個更為嚴格和複雜的模式,它將應用程式的各個職責切分得更細,非常適合大型且需要高度可測試性的專案。
- View (視圖):與 MVVM 的 View 類似,只負責顯示 UI 和將使用者輸入傳給 Presenter。
- Interactor (互動器):包含所有的業務邏輯和資料操作,如與 Realm 的互動。它不關心 UI,只專注於資料層。
- Presenter (展示者):負責從 Interactor 取得資料,將其處理成 View 可以顯示的格式,然後傳給 View。Presenter 就像是 Controller 或 ViewModel 的角色,但職責更為明確。
- Entity (實體):代表應用程式的資料模型,例如您的鬧鐘物件。
- Router (路由):負責處理不同畫面的導航邏輯,如從鬧鐘列表頁跳轉到新增鬧鐘頁面。
VIPER 的優點在於它的職責劃分非常清晰,每個元件都只做一件事情,這使得單元測試變得非常容易。但相對地,它會讓專案的檔案數量和複雜度大幅增加,對於中小型專案來說,可能有點過度設計。
針對您的鬧鐘 App,MVVM 會是一個非常好的選擇。它既能有效解決您
MainViewController
臃腫的問題,又能保持相對簡單的架構,並且非常利於日後的單元測試。您只需要將Realm
的資料操作和文字格式化邏輯移到一個獨立的ViewModel
類別中,您的MainViewController
就能大幅瘦身,並專注於 UI 的協調工作。如果您對這些架構模式有進一步的疑問,或是想知道如何將您的專案重構成 MVVM,我們可以再深入討論。
架構模式 | 核心概念 | 優點 | 缺點 | 適合情境 |
---|---|---|---|---|
MVC | 視圖、模型、控制器 | 簡單、直觀、Apple 官方推薦 | ViewController 易臃腫 |
小型專案或快速原型開發 |
MVVM | 視圖、視圖模型、模型 | 輕量化 ViewController 、易於測試 |
存在 ViewModel 和 View 的綁定關係,需額外處理 |
中型專案或需要更好測試性時 |
VIPER | 視圖、互動器、展示者、實體、路由 | 職責劃分最清晰、單元測試最容易 | 複雜度高、檔案數量多、學習曲線陡峭 | 大型專案或高度嚴謹的團隊開發 |
AI 的回答為我打開了一扇新的大門。它不僅清晰地指出了我目前在 MainViewController.swift
中遇到的「Massive View Controller」問題,還一次講解了三種業界主流的解決方案:MVC 的正確實踐、更現代的 MVVM,以及更嚴謹的 VIPER。
對比這三種模式,我有了非常清晰的結論:
MainViewController
中抽離,使得 Controller 能夠回歸單純的 View 角色。雖然我們在這個系列中不會真的動手重構鬧鐘APP,但在未來的開發旅程中,MVVM 將是我的首選方案。